package com.mlizhi.base.dao.async;

import android.database.sqlite.SQLiteDatabase;
import com.mlizhi.base.dao.AbstractDao;
import com.mlizhi.base.dao.AbstractDaoSession;
import com.mlizhi.base.dao.DaoException;
import com.mlizhi.base.dao.async.AsyncOperation.OperationType;
import com.mlizhi.base.dao.query.Query;

import java.util.concurrent.Callable;

/**
 * Asynchronous interface to entity operations. All operations will enqueued a @link {@link AsyncOperation} and return
 * immediately (fine to call on the UI/main thread). The queue will be processed in a (single) background thread. The
 * processing order is the call order of the operations. It's possible to start multiple AsyncSessions that will
 * execute
 * concurrently.
 *
 * @author Markus
 * @see AbstractDaoSession#startAsyncSession()
 */
// Facade to AsyncOperationExecutor: prepares operations and delegates work to AsyncOperationExecutor.
public class AsyncSession {
private final AbstractDaoSession daoSession;
private final AsyncOperationExecutor executor;
private int sessionFlags;

public AsyncSession(AbstractDaoSession daoSession) {
this.daoSession = daoSession;
this.executor = new AsyncOperationExecutor();
}

public int getMaxOperationCountToMerge() {
return executor.getMaxOperationCountToMerge();
}

public void setMaxOperationCountToMerge(int maxOperationCountToMerge) {
executor.setMaxOperationCountToMerge(maxOperationCountToMerge);
}

public int getWaitForMergeMillis() {
return executor.getWaitForMergeMillis();
}

public void setWaitForMergeMillis(int waitForMergeMillis) {
executor.setWaitForMergeMillis(waitForMergeMillis);
}

public AsyncOperationListener getListener() {
return executor.getListener();
}

public void setListener(AsyncOperationListener listener) {
executor.setListener(listener);
}

public AsyncOperationListener getListenerMainThread() {
return executor.getListenerMainThread();
}

public void setListenerMainThread(AsyncOperationListener listenerMainThread) {
executor.setListenerMainThread(listenerMainThread);
}

public boolean isCompleted() {
return executor.isCompleted();
}

/**
 * Waits until all enqueued operations are complete. If the thread gets interrupted, any
 * {@link InterruptedException} will be rethrown as a {@link DaoException}.
 */
public void waitForCompletion() {
executor.waitForCompletion();
}

/**
 * Waits until all enqueued operations are complete, but at most the given amount of milliseconds. If the thread
 * gets interrupted, any {@link InterruptedException} will be rethrown as a {@link DaoException}.
 *
 * @return true if operations completed in the given time frame.
 */
public boolean waitForCompletion(int maxMillis) {
return executor.waitForCompletion(maxMillis);
}

/** Asynchronous version of {@link AbstractDao#insert(Object)}. */
public AsyncOperation insert(Object entity) {
return insert(entity, 0);
}

/** Asynchronous version of {@link AbstractDao#insert(Object)}. */
public AsyncOperation insert(Object entity, int flags) {
return enqueueEntityOperation(OperationType.Insert, entity, flags);
}

/** Asynchronous version of {@link AbstractDao#insertInTx(Object...)}. */
public <E> AsyncOperation insertInTx(Class<E> entityClass, E... entities) {
return insertInTx(entityClass, 0, entities);
}

/** Asynchronous version of {@link AbstractDao#insertInTx(Object...)}. */
public <E> AsyncOperation insertInTx(Class<E> entityClass, int flags, E... entities) {
return enqueEntityOperation(OperationType.InsertInTxArray, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#insertInTx(Iterable)}. */
public <E> AsyncOperation insertInTx(Class<E> entityClass, Iterable<E> entities) {
return insertInTx(entityClass, entities, 0);
}

/** Asynchronous version of {@link AbstractDao#insertInTx(Iterable)}. */
public <E> AsyncOperation insertInTx(Class<E> entityClass, Iterable<E> entities, int flags) {
return enqueEntityOperation(OperationType.InsertInTxIterable, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#insertOrReplace(Object)}. */
public AsyncOperation insertOrReplace(Object entity) {
return insertOrReplace(entity, 0);
}

/** Asynchronous version of {@link AbstractDao#insertOrReplace(Object)}. */
public AsyncOperation insertOrReplace(Object entity, int flags) {
return enqueueEntityOperation(OperationType.InsertOrReplace, entity, flags);
}

/** Asynchronous version of {@link AbstractDao#insertOrReplaceInTx(Object...)}. */
public <E> AsyncOperation insertOrReplaceInTx(Class<E> entityClass, E... entities) {
return insertOrReplaceInTx(entityClass, 0, entities);
}

/** Asynchronous version of {@link AbstractDao#insertOrReplaceInTx(Object...)}. */
public <E> AsyncOperation insertOrReplaceInTx(Class<E> entityClass, int flags, E... entities) {
return enqueEntityOperation(OperationType.InsertOrReplaceInTxArray, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#insertOrReplaceInTx(Iterable)}. */
public <E> AsyncOperation insertOrReplaceInTx(Class<E> entityClass, Iterable<E> entities) {
return insertOrReplaceInTx(entityClass, entities, 0);
}

/** Asynchronous version of {@link AbstractDao#insertOrReplaceInTx(Iterable)}. */
public <E> AsyncOperation insertOrReplaceInTx(Class<E> entityClass, Iterable<E> entities, int flags) {
return enqueEntityOperation(OperationType.InsertOrReplaceInTxIterable, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#update(Object)}. */
public AsyncOperation update(Object entity) {
return update(entity, 0);
}

/** Asynchronous version of {@link AbstractDao#update(Object)}. */
public AsyncOperation update(Object entity, int flags) {
return enqueueEntityOperation(OperationType.Update, entity, flags);
}

/** Asynchronous version of {@link AbstractDao#updateInTx(Object...)}. */
public <E> AsyncOperation updateInTx(Class<E> entityClass, E... entities) {
return updateInTx(entityClass, 0, entities);
}

/** Asynchronous version of {@link AbstractDao#updateInTx(Object...)}. */
public <E> AsyncOperation updateInTx(Class<E> entityClass, int flags, E... entities) {
return enqueEntityOperation(OperationType.UpdateInTxArray, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#updateInTx(Iterable)}. */
public <E> AsyncOperation updateInTx(Class<E> entityClass, Iterable<E> entities) {
return updateInTx(entityClass, entities, 0);
}

/** Asynchronous version of {@link AbstractDao#updateInTx(Iterable)}. */
public <E> AsyncOperation updateInTx(Class<E> entityClass, Iterable<E> entities, int flags) {
return enqueEntityOperation(OperationType.UpdateInTxIterable, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#delete(Object)}. */
public AsyncOperation delete(Object entity) {
return delete(entity, 0);
}

/** Asynchronous version of {@link AbstractDao#delete(Object)}. */
public AsyncOperation delete(Object entity, int flags) {
return enqueueEntityOperation(OperationType.Delete, entity, flags);
}

/** Asynchronous version of {@link AbstractDao#deleteByKey(Object)}. */
public AsyncOperation deleteByKey(Object key) {
return deleteByKey(key, 0);
}

/** Asynchronous version of {@link AbstractDao#deleteByKey(Object)}. */
public AsyncOperation deleteByKey(Object key, int flags) {
return enqueueEntityOperation(OperationType.DeleteByKey, key, flags);
}

/** Asynchronous version of {@link AbstractDao#deleteInTx(Object...)}. */
public <E> AsyncOperation deleteInTx(Class<E> entityClass, E... entities) {
return deleteInTx(entityClass, 0, entities);
}

/** Asynchronous version of {@link AbstractDao#deleteInTx(Object...)}. */
public <E> AsyncOperation deleteInTx(Class<E> entityClass, int flags, E... entities) {
return enqueEntityOperation(OperationType.DeleteInTxArray, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#deleteInTx(Iterable)}. */
public <E> AsyncOperation deleteInTx(Class<E> entityClass, Iterable<E> entities) {
return deleteInTx(entityClass, entities, 0);
}

/** Asynchronous version of {@link AbstractDao#deleteInTx(Iterable)}. */
public <E> AsyncOperation deleteInTx(Class<E> entityClass, Iterable<E> entities, int flags) {
return enqueEntityOperation(OperationType.DeleteInTxIterable, entityClass, entities, flags);
}

/** Asynchronous version of {@link AbstractDao#deleteAll()}. */
public <E> AsyncOperation deleteAll(Class<E> entityClass) {
return deleteAll(entityClass, 0);
}

/** Asynchronous version of {@link AbstractDao#deleteAll()}. */
public <E> AsyncOperation deleteAll(Class<E> entityClass, int flags) {
return enqueEntityOperation(OperationType.DeleteAll, entityClass, null, flags);
}

/** Asynchronous version of {@link AbstractDaoSession#runInTx(Runnable)}. */
public AsyncOperation runInTx(Runnable runnable) {
return runInTx(runnable, 0);
}

/** Asynchronous version of {@link AbstractDaoSession#runInTx(Runnable)}. */
public AsyncOperation runInTx(Runnable runnable, int flags) {
return enqueueDatabaseOperation(OperationType.TransactionRunnable, runnable, flags);
}

/** Asynchronous version of {@link AbstractDaoSession#callInTx(java.util.concurrent.Callable)}. */
public AsyncOperation callInTx(Callable<?> callable) {
return callInTx(callable, 0);
}

/** Asynchronous version of {@link AbstractDaoSession#callInTx(java.util.concurrent.Callable)}. */
public AsyncOperation callInTx(Callable<?> callable, int flags) {
return enqueueDatabaseOperation(OperationType.TransactionCallable, callable, flags);
}

/** Asynchronous version of {@link Query#list()}. */
public AsyncOperation queryList(Query<?> query) {
return queryList(query, 0);
}

/** Asynchronous version of {@link Query#list()}. */
public AsyncOperation queryList(Query<?> query, int flags) {
return enqueueDatabaseOperation(OperationType.QueryList, query, flags);
}

/** Asynchronous version of {@link Query#unique()}. */
public AsyncOperation queryUnique(Query<?> query) {
return queryUnique(query, 0);
}

/** Asynchronous version of {@link Query#unique()}. */
public AsyncOperation queryUnique(Query<?> query, int flags) {
return enqueueDatabaseOperation(OperationType.QueryUnique, query, flags);
}

/** Asynchronous version of {@link AbstractDao#load(Object)}. */
public AsyncOperation load(Class<?> entityClass, Object key) {
return load(entityClass, key, 0);
}

/** Asynchronous version of {@link AbstractDao#load(Object)}. */
public AsyncOperation load(Class<?> entityClass, Object key, int flags) {
return enqueEntityOperation(OperationType.Load, entityClass, key, flags);
}

/** Asynchronous version of {@link AbstractDao#loadAll()}. */
public AsyncOperation loadAll(Class<?> entityClass) {
return loadAll(entityClass, 0);
}

/** Asynchronous version of {@link AbstractDao#loadAll()}. */
public AsyncOperation loadAll(Class<?> entityClass, int flags) {
return enqueEntityOperation(OperationType.LoadAll, entityClass, null, flags);
}

/** Asynchronous version of {@link AbstractDao#count()}. */
public AsyncOperation count(Class<?> entityClass) {
return count(entityClass, 0);
}

/** Asynchronous version of {@link AbstractDao#count()}. */
public AsyncOperation count(Class<?> entityClass, int flags) {
return enqueEntityOperation(OperationType.Count, entityClass, null, flags);
}

/** Asynchronous version of {@link AbstractDao#refresh(Object)}. */
public AsyncOperation refresh(Object entity) {
return refresh(entity, 0);
}

/** Asynchronous version of {@link AbstractDao#refresh(Object)}. */
public AsyncOperation refresh(Object entity, int flags) {
return enqueueEntityOperation(OperationType.Refresh, entity, flags);
}

private AsyncOperation enqueueDatabaseOperation(OperationType type, Object param, int flags) {
SQLiteDatabase database = daoSession.getDatabase();
AsyncOperation operation = new AsyncOperation(type, null, database, param, flags | sessionFlags);
executor.enqueue(operation);
return operation;
}

private AsyncOperation enqueueEntityOperation(OperationType type, Object entity, int flags) {
return enqueEntityOperation(type, entity.getClass(), entity, flags);
}

private <E> AsyncOperation enqueEntityOperation(OperationType type, Class<E> entityClass, Object param, int flags) {
AbstractDao<?, ?> dao = daoSession.getDao(entityClass);
AsyncOperation operation = new AsyncOperation(type, dao, null, param, flags | sessionFlags);
executor.enqueue(operation);
return operation;
}

/** {@link com.mlizhi.base.dao.async.AsyncOperation} flags set for all operations (will be ORed with call flags). */
public int getSessionFlags() {
return sessionFlags;
}

/** {@link com.mlizhi.base.dao.async.AsyncOperation} flags set for all operations (will be ORed with call flags). */
public void setSessionFlags(int sessionFlags) {
this.sessionFlags = sessionFlags;
}
}
